// Copyright 2012 Google, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

#include <iostream>

#include "inetsocket.h"

#ifndef SOL_IP
#  define SOL_IP  IPPROTO_IP
#endif

#ifndef SOL_IPV6
#  define SOL_IPV6  IPPROTO_IPV6
#endif

// From linux/in6.h.
#ifndef IPV6_TRANSPARENT
#  define IPV6_TRANSPARENT        75
#endif

#ifndef IPV6_2292PKTINFO
#  define IPV6_2292PKTINFO        IPV6_PKTINFO
#endif


namespace PerRequestIPv6Address {
namespace {

void SetNonblocking(int fd) {
  int flags = ::fcntl(fd, F_GETFL, 0);
  ::fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

int GetIntegerSocketOption(int fd, int level, int option) {
  int value = -1;
  socklen_t len = sizeof(value);
  errno = 0;
  if (::getsockopt(fd, level, option, &value, &len) != 0) {
    value = -1;
  }
  return value;
}

int SetIntegerSocketOption(int fd, int level, int option, int value) {
  errno = 0;
  return ::setsockopt(fd, level, option, &value, sizeof(value));
}

bool NewLocalInetSocket(InetSocket *sock, int type, uint16_t port) {
  if (!sock) {
    return false;
  }

  errno = 0;
  *sock = ::socket(AF_INET6, type, 0);
  if (sock->fd() < 0) {
    return false;
  }

  if (!sock->enableReuseAddr()) {
    std::cerr << "Failed to set SO_REUSEADDR: " << strerror(errno)
              << std::endl;
  }

  struct sockaddr_in6 sin6;
  sin6.sin6_family = AF_INET6;
  sin6.sin6_port = htons(port);
  sin6.sin6_addr = in6addr_any;
  struct sockaddr *sa_ptr = reinterpret_cast<struct sockaddr *>(&sin6);
  errno = 0;
  if (bind(sock->fd(), sa_ptr, sizeof(sin6)) < 0) {
    std::cerr << "Failed to bind(2): " << strerror(errno)
              << std::endl;
    *sock = -1;
    return false;
  }

  std::cerr << "Local socket ready: " << sock->fd() << std::endl;
  return (sock->fd() > -1);
}

bool NewUpstreamInetSocket(InetSocket *sock, int type, uint16_t port) {
  if (!sock) {
    return false;
  }

  errno = 0;
  *sock = ::socket(AF_INET6, type, 0);
  if (sock->fd() < 0) {
    return false;
  }

  if (!sock->enableIPv6Only()) {
    std::cerr << "Failed to set IPV6_V6ONLY: " << strerror(errno)
              << std::endl;
    *sock = -1;
    return false;
  }

  if (type == SOCK_DGRAM || type == SOCK_RAW) {
    if (!sock->requestPacketInfo()) {
      std::cerr << "Failed to set IP{,V6}_PKTINFO: " << strerror(errno)
                << std::endl;
      *sock = -1;
      return false;
    }
  }

  if (!sock->enableLinuxIPFreebind()) {
    std::cerr << "Failed to set IP_FREEBIND: " << strerror(errno)
              << std::endl;
    *sock = -1;
    return false;
  }

  // This requires root.  At some point, the above IP_FREEBIND should suffice.
  if (!sock->enableLinuxIPTransparent()) {
    std::cerr << "Failed to set IPV6_TRANSPARENT: " << strerror(errno)
              << std::endl;
  }

  struct sockaddr_in6 sin6;
  sin6.sin6_family = AF_INET6;
  sin6.sin6_port = htons(port);
  sin6.sin6_addr = in6addr_any;
  struct sockaddr *sa_ptr = reinterpret_cast<struct sockaddr *>(&sin6);
  errno = 0;
  if (bind(sock->fd(), sa_ptr, sizeof(sin6)) < 0) {
    std::cerr << "Failed to bind(2): " << strerror(errno)
              << std::endl;
    *sock = -1;
    return false;
  }

  std::cerr << "Upstream socket ready: " << sock->fd() << std::endl;
  return (sock->fd() > -1);
}

}  // anonymous namespace


InetSocket::InetSocket() : fd_(-1) {}

InetSocket::InetSocket(int fd) : fd_(fd) {
  SetNonblocking(fd_);
  forceOutOfBandInline();
}

InetSocket::~InetSocket() {
  if (fd_ > -1) {
    ::close(fd_);
  }
  fd_ = -1;
}

InetSocket & InetSocket::operator=(int fd) {
  if (fd_ > -1) {
    ::close(fd_);
  }
  fd_ = fd;
  SetNonblocking(fd_);
  forceOutOfBandInline();
  return *this;
}

bool InetSocket::requestPacketInfo() {
  switch (addressFamily()) {
    case AF_INET:
      return (SetIntegerSocketOption(fd_, SOL_IP, IP_PKTINFO, 1) == 0);
      break;
    case AF_INET6: {
      bool pkt6 =
          (SetIntegerSocketOption(fd_, SOL_IPV6, IPV6_2292PKTINFO, 1) == 0);
      if (!isIPv6Only() && pkt6) {
        return (SetIntegerSocketOption(fd_, SOL_IP, IP_PKTINFO, 1) == 0);
      }
      return pkt6;
      break;
    }
  }
  return false;
}

bool InetSocket::enableIPv6Only() {
  return (SetIntegerSocketOption(fd_, SOL_IPV6, IPV6_V6ONLY, 1) == 0);
}

bool InetSocket::isIPv6Only() {
  return (GetIntegerSocketOption(fd_, SOL_IPV6, IPV6_V6ONLY) == 1);
}

bool InetSocket::enableLinuxIPFreebind() {
  return (SetIntegerSocketOption(fd_, SOL_IP, IP_FREEBIND, 1) == 0);
}

bool InetSocket::enableLinuxIPTransparent() {
  return (SetIntegerSocketOption(fd_, SOL_IPV6, IPV6_TRANSPARENT, 1) == 0);
}

bool InetSocket::enableReuseAddr() {
  return (SetIntegerSocketOption(fd_, SOL_SOCKET, SO_REUSEADDR, 1) == 0);
}

bool InetSocket::forceOutOfBandInline() {
  return (SetIntegerSocketOption(fd_, SOL_SOCKET, SO_OOBINLINE, 1) == 0);
}

int InetSocket::addressFamily() const {
  return (GetIntegerSocketOption(fd_, SOL_SOCKET, SO_DOMAIN));
}

uint16_t InetSocket::localPort() const {
  struct sockaddr_storage ss;
  socklen_t addrlen = sizeof(sockaddr_storage);
  memset(&ss, 0, addrlen);
  struct sockaddr *sa_ptr = reinterpret_cast<struct sockaddr *>(&ss);
  errno = 0;
  if (::getsockname(fd_, sa_ptr, &addrlen) == 0) {
    switch (ss.ss_family) {
      case AF_INET: {
        struct sockaddr_in *p = reinterpret_cast<struct sockaddr_in *>(&ss);
        return ntohs(p->sin_port);
        break;
      }
      case AF_INET6: {
        struct sockaddr_in6 *p = reinterpret_cast<struct sockaddr_in6 *>(&ss);
        return ntohs(p->sin6_port);
        break;
      }
    }
  }
  return 0;
}


bool NewUpstreamUDPSocket(InetSocket *sock, uint16_t port) {
  return NewUpstreamInetSocket(sock, SOCK_DGRAM, port);
}

bool NewUpstreamTCPSocket(InetSocket *sock, uint16_t port) {
  return NewUpstreamInetSocket(sock, SOCK_STREAM, port);
}

void TestSendFromRandomSourceAddress(InetSocket *sock) {
  if (!sock) {
    return;
  }

#if 0
#endif
  struct msghdr msg = {0};
  struct cmsghdr *cmsg;
  // ancillary data buffer
  char buf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
  memset(buf, 0, sizeof(buf));

#if 0
#endif
  struct in6_pktinfo pkt6 = { IN6ADDR_ANY_INIT, 0 };
  // Make up something from 2001:db8::/32 for now.
#if 0
#endif
  pkt6.ipi6_addr.s6_addr[0] = 0x20;
  pkt6.ipi6_addr.s6_addr[1] = 0x01;
  pkt6.ipi6_addr.s6_addr[2] = 0x0d;
  pkt6.ipi6_addr.s6_addr[3] = 0xb8;
  pkt6.ipi6_addr.s6_addr[15] = 0x01;

  msg.msg_control = buf;
  msg.msg_controllen = sizeof(buf);
  cmsg = CMSG_FIRSTHDR(&msg);
  cmsg->cmsg_level = SOL_IPV6;
  cmsg->cmsg_type = IPV6_PKTINFO;
  cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));

  // Initialize the payload.
  struct in6_pktinfo *pkt_ptr =
      reinterpret_cast<struct in6_pktinfo *>(CMSG_DATA(cmsg));
  memcpy(pkt_ptr, &pkt6, sizeof(struct in6_pktinfo));
  // Sum of the length of all control messages in the buffer.
  msg.msg_controllen = cmsg->cmsg_len;

  struct sockaddr_in6 dst6;
  dst6.sin6_family = AF_INET6;
  dst6.sin6_port = htons(53);
  dst6.sin6_addr = in6addr_any;
  // Ask HE, 2001:470:20::2.
  dst6.sin6_addr.s6_addr[0] = 0x20;
  dst6.sin6_addr.s6_addr[1] = 0x01;
  dst6.sin6_addr.s6_addr[2] = 0x04;
  dst6.sin6_addr.s6_addr[3] = 0x70;
  dst6.sin6_addr.s6_addr[5] = 0x20;
  dst6.sin6_addr.s6_addr[15] = 0x02;
  msg.msg_name = reinterpret_cast<void *>(&dst6);
  msg.msg_namelen = sizeof(struct sockaddr_in6);

#if 0
#endif
  struct iovec iovs[1];
  iovs[0].iov_base = (void *)"hello";
  iovs[0].iov_len = 6;
  msg.msg_iov = iovs;
  msg.msg_iovlen = 1;

  errno = 0;
  ssize_t sent = ::sendmsg(sock->fd(), &msg, MSG_NOSIGNAL | MSG_DONTWAIT);
  std::cerr << "Sent bytes: " << sent << ", " << strerror(errno) << std::endl;
}

bool NewLocalUDPSocket(InetSocket *sock, uint16_t port) {
  return NewLocalInetSocket(sock, SOCK_DGRAM, port);
}

bool NewLocalTCPSocket(InetSocket *sock, uint16_t port) {
  return NewLocalInetSocket(sock, SOCK_STREAM, port);
}

}  // namespace PerRequestIPv6Address
